﻿using System;
using System.Runtime.CompilerServices;
using System.Threading;
using Cysharp.Threading.Tasks;
using FairyGUI;

namespace GameFramework.UI.FairyGUI
{
    /// <summary>
    /// FairyGUI的GTweener扩展
    /// </summary>
    public static class FairyGUIGTweenerExtensions
    {
        public enum GTweenCancelBehaviour
        {
            Kill,
            Complete,
            CancelAwait,
        }

        /// <summary>
        /// 将FairyGUI的GTweener转换为UniTask
        /// </summary>
        public static UniTask ToUniTask(this GTweener gTweener, GTweenCancelBehaviour cancelBehaviour = GTweenCancelBehaviour.Kill, CancellationToken cancellationToken = default)
        {
            if (gTweener == null)
                throw new ArgumentNullException(nameof(gTweener));
            
            if (gTweener.id == 0)
                return UniTask.CompletedTask;

            return new UniTask(GTweenerTaskSource.Create(gTweener, cancelBehaviour, cancellationToken, out var token), token);
        }

        /// <summary>
        /// 将FairyGUI的GTweener转换为UniTask
        /// </summary>
        public static UniTask WithCancellation(this GTweener gTweener, CancellationToken cancellationToken)
        {
            return gTweener.ToUniTask(GTweenCancelBehaviour.Kill, cancellationToken);
        }

        private sealed class GTweenerTaskSource : IUniTaskSource, ITaskPoolNode<GTweenerTaskSource>
        {
            private static TaskPool<GTweenerTaskSource> pool;
            private GTweenerTaskSource m_NextNode;
            public ref GTweenerTaskSource NextNode => ref m_NextNode;

            static GTweenerTaskSource()
            {
                TaskPool.RegisterSizeGetter(typeof(GTweenerTaskSource), () => pool.Size);
            }

            private GTweener m_GTweener;
            private UniTaskCompletionSourceCore<AsyncUnit> m_Core;

            private CancellationToken m_CancellationToken;
            private GTweenCancelBehaviour m_CancelBehaviour;

            private GTweenCallback m_OriginalCompleteAction;
            private GTweenCallback m_OriginalUpdateAction;
            private GTweenCallback1 m_OriginalCompleteAction1;
            private GTweenCallback1 m_OriginalUpdateAction1;

            readonly GTweenCallback1 m_OnCompleteCallbackDelegate;
            readonly GTweenCallback1 m_OnUpdateDelegate;

            public GTweenerTaskSource()
            {
                m_OnCompleteCallbackDelegate = OnCompleteCallbackDelegate;
                m_OnUpdateDelegate = OnUpdate;
            }

            public static IUniTaskSource Create(GTweener tween, GTweenCancelBehaviour cancelBehaviour, CancellationToken cancellationToken, out short token)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    DoCancelBeforeCreate(tween, cancelBehaviour);
                    return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
                }

                if (!pool.TryPop(out var result))
                {
                    result = new GTweenerTaskSource();
                }

                result.m_GTweener = tween;

                result.m_CancelBehaviour = cancelBehaviour;
                result.m_CancellationToken = cancellationToken;

                result.m_OriginalUpdateAction = tween.onUpdate;
                result.m_OriginalCompleteAction1 = tween.onUpdate1;
                result.m_OriginalCompleteAction = tween.onComplete;
                result.m_OriginalCompleteAction1 = tween.onComplete1;

                if (result.m_OriginalCompleteAction1 == result.m_OnUpdateDelegate)
                    result.m_OriginalCompleteAction1 = null;

                if (result.m_OriginalUpdateAction1 == result.m_OnCompleteCallbackDelegate)
                    result.m_OriginalUpdateAction1 = null;

                tween.OnUpdate((GTweenCallback)null);
                tween.OnUpdate(result.m_OnUpdateDelegate);

                tween.OnComplete((GTweenCallback)null);
                tween.OnComplete(result.m_OnCompleteCallbackDelegate);

                TaskTracker.TrackActiveTask(result, 3);

                token = result.m_Core.Version;
                return result;
            }

            private void OnUpdate(GTweener tweener)
            {
                m_OriginalUpdateAction?.Invoke();
                m_OriginalUpdateAction1?.Invoke(tweener);

                if (!m_CancellationToken.IsCancellationRequested)
                {
                    return;
                }

                switch (m_CancelBehaviour)
                {
                    case GTweenCancelBehaviour.Kill:
                        m_GTweener.Kill(false);
                        break;
                    case GTweenCancelBehaviour.Complete:
                        m_GTweener.Kill(true);
                        return;
                    case GTweenCancelBehaviour.CancelAwait:
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }

                m_Core.TrySetCanceled(m_CancellationToken);
            }

            private void OnCompleteCallbackDelegate(GTweener tweener)
            {
                m_OriginalCompleteAction?.Invoke();
                m_OriginalCompleteAction1?.Invoke(tweener);
                m_Core.TrySetResult(AsyncUnit.Default);
            }

            private static void DoCancelBeforeCreate(GTweener tween, GTweenCancelBehaviour tweenCancelBehaviour)
            {
                switch (tweenCancelBehaviour)
                {
                    case GTweenCancelBehaviour.Kill:
                        tween.Kill(false);
                        break;
                    case GTweenCancelBehaviour.Complete:
                        tween.Kill(true);
                        break;
                    case GTweenCancelBehaviour.CancelAwait:
                        break;
                    default:
                        throw new ArgumentOutOfRangeException(nameof(tweenCancelBehaviour), tweenCancelBehaviour, null);
                }
            }

            public void GetResult(short token)
            {
                try
                {
                    m_Core.GetResult(token);
                }
                finally
                {
                    TryReturn();
                }
            }

            public UniTaskStatus GetStatus(short token)
            {
                return m_Core.GetStatus(token);
            }

            public UniTaskStatus UnsafeGetStatus()
            {
                return m_Core.UnsafeGetStatus();
            }

            public void OnCompleted(Action<object> continuation, object state, short token)
            {
                m_Core.OnCompleted(continuation, state, token);
            }

            bool TryReturn()
            {
                TaskTracker.RemoveTracking(this);
                m_Core.Reset();

                if (m_GTweener.onUpdate1 == m_OnUpdateDelegate)
                {
                    m_GTweener.OnUpdate(m_OriginalUpdateAction);
                    m_GTweener.OnUpdate(m_OriginalUpdateAction1);
                }

                if (m_GTweener.onComplete1 == m_OnCompleteCallbackDelegate)
                {
                    m_GTweener.OnComplete(m_OriginalCompleteAction);
                    m_GTweener.OnComplete(m_OriginalCompleteAction1);
                }

                m_GTweener = default;
                m_CancellationToken = default;
                m_OriginalCompleteAction = default;
                m_OriginalCompleteAction1 = default;
                m_OriginalUpdateAction = default;
                m_OriginalUpdateAction1 = default;

                return pool.TryPush(this);
            }
        }
    }
}